package zone

import (
	"errors"
	"github.com/Unixeno/TheMoon/planet/orm"
	"github.com/Unixeno/TheMoon/planet/orm/model"
	"github.com/jinzhu/gorm"
	"github.com/rs/xid"
	"time"
)

var ExistError = errors.New("zone already exist")

type ZoneList struct {
	ID        string
	Name      string
	ZoneFile  string
	State     model.ZoneState
	CreatedAt time.Time
	MoonCount int
	Version   int
}

type Dao struct {
	model.Zone
	db *gorm.DB
}

func New() Dao {
	return Dao{
		db: orm.DB,
	}
}

func (m *Dao) Create() (id string, err error) {
	if m.db.Where(m.Zone).Take(&m.Zone).RecordNotFound() {
		m.Zone.ID = xid.New().String()
		if m.Zone.Name == "" {
			m.Zone.Name = m.Zone.ID
		}
		result := m.db.Create(&m.Zone)
		return m.Zone.ID, result.Error
	}
	return "", ExistError
}

func (m *Dao) List() (list []ZoneList, err error) {
	err = m.db.Table("zones").Select("zones.*, count(*) moon_count").Group("id").Find(&list).Error
	return
}

func (m *Dao) Count() (count int) {
	m.db.Model(&m.Zone).Count(&count)
	return
}

func (m *Dao) Delete() error {
	result := m.db.Delete(&m.Zone)
	if result.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	} else {
		return nil
	}
}

func (m *Dao) GetDetails() (model.Zone, error) {
	err := m.db.Preload("Moons").Take(&m.Zone).Error
	return m.Zone, err
}

func (m *Dao) RemoveMoon(moons []string) error {
	if len(moons) == 0 {
		return nil
	}
	tx := m.db.Begin()
	if tx.Take(&m.Zone).RecordNotFound() {
		return gorm.ErrRecordNotFound
	}
	moon := model.Moon{}
	for _, val := range moons {
		moon.ID = val
		if err := tx.Model(&m.Zone).Association("moons").Delete(moon).Error; err != nil {
			tx.Rollback()
			return err
		}
	}
	return tx.Commit().Error
}

func (m *Dao) AddMoon(moons []string) error {
	if len(moons) == 0 {
		return nil
	}
	if m.db.Take(&m.Zone).RecordNotFound() {
		return gorm.ErrRecordNotFound
	}
	tx := m.db.Begin()
	moon := model.Moon{}
	for _, val := range moons {
		if err := m.db.Where("id = ?", val).Take(&moon).Error; err != nil {
			tx.Rollback()
			return errors.New("moon '" + val + "' dose not exist")
		}
		tx.Model(&m.Zone).Association("moons").Append(moon)
	}
	return tx.Commit().Error
}

func (m *Dao) Get() error {
	return m.db.Take(&m.Zone).Error
}

func (m *Dao) Save() error {
	return m.db.Save(m.Zone).Error
}

func (m *Dao) Deploy(version int) error {
	if _, err := m.ReadZonefile(version); err != nil {
		return err
	}
	return m.db.Model(&m.Zone).Update("version", version).Error
}

func (m *Dao) GetZonefile(version int) (zonefile model.Zonefile, err error) {
	zonefile.Version = version
	zonefile.ZoneID = m.ID
	err = m.db.Take(&zonefile).Error
	return
}

func (m *Dao) ListZonefile(begin, limit int32, desc bool) (zonefiles []*model.Zonefile, err error) {
	var db *gorm.DB
	if limit == 0 {
		db = m.db
	} else {
		db = m.db.Limit(limit)
	}

	if desc {
		if begin != 0 {
			db = db.Where("version <= ?", begin)
		}
		err = db.Where("zone_id = ?", m.ID).Order("version desc").Find(&zonefiles).Error
	} else {
		err = db.Where("zone_id = ?", m.ID).Where("version >= ?", begin).Find(&zonefiles).Error
	}
	return
}

func (m *Dao) AddZonefile(rules string) (version int, err error) {
	zonefile := model.Zonefile{}
	err = m.db.Order("version desc").Where(model.Zonefile{ZoneID: m.ID}).Take(&zonefile).Error
	if err != nil && !gorm.IsRecordNotFoundError(err) {
		return
	}
	zonefile.ZoneID = m.ID
	zonefile.Version += 1
	zonefile.Rules = rules
	zonefile.CreatedAt = time.Time{}
	err = m.db.Create(&zonefile).Error
	return zonefile.Version, err
}

func (m *Dao) ReadZonefile(version int) (zonefile model.Zonefile, err error) {
	zonefile.Version = version
	zonefile.ZoneID = m.ID
	err = m.db.Take(&zonefile).Error
	return
}
